﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Data.Linq;
using System.Data.Linq.Mapping;

namespace DataLayer
{
    public class ObjectUpdater
    {
        LinqMappingHelper _mapping;
        DataContext _dataContext;

        public ObjectUpdater(DataContext dataContext)
        {
            _mapping = new LinqMappingHelper(dataContext.Mapping);
            _dataContext = dataContext;
        }

        public void Update(object source, object destination, bool includeChildren)
        {
            Update(source, destination, includeChildren, 1);
        }

        public void Update(object source, object destination, bool includeChildren, int maxLevel)
        {
            foreach (PropertyInfo pi in _mapping.GetDatabaseProperties(source.GetType()))
            {
                pi.SetValue(destination, pi.GetValue(source, null), null);
            }
            if (includeChildren)
            {
                int currentLevel = 0;
                while (currentLevel < maxLevel)
                {
                    currentLevel++;
                    foreach (var md in _mapping.GetAssociationPropertiesMetaData(source.GetType()))
                    {
                        //call UpdateChildCollection as it's generic and we don't know the type at 
                        //compile time we need to call it as follows:
                        MethodInfo method = typeof(ObjectUpdater).GetMethod("UpdateChildCollection");
                        MethodInfo generic = method.MakeGenericMethod(new Type[] { md.Association.OtherType.Type });
                        PropertyInfo pi = _mapping.GetPropertyInfoFromMetaData(source.GetType(), md);
                        object s = pi.GetValue(source, null);
                        object d = pi.GetValue(destination, null);
                        // Here's the magic for the recursion to occur: 
                        // test if currentLevel != maxLevel and pass that to the UpdateChildCollection method
                        generic.Invoke(this, new object[] { s, d, ((currentLevel != maxLevel)?true:false) });
                    }
                }
            }
        }

        public void UpdateChildCollection<T>(EntitySet<T> sourceCollection, EntitySet<T> destinationCollection, bool includeChildren) where T: class, new()
        {
            //get a list of all the ids in the 2 collections
            var sourceIds = sourceCollection.Select(x => _mapping.GetPrimaryKeyValue(x)); 
            var destinationIds = destinationCollection.Select(x => _mapping.GetPrimaryKeyValue(x));
            
            //get the ids of the entities to be added and deleted
            var additions = from s in sourceIds
                            where (destinationIds.Contains(s) == false)
                            select s;
            var deletions = (from d in destinationIds
                            where (sourceIds.Contains(d) == false)
                            select d).ToList();
            

            //delete all the entities that are in the destination but not in the source
            while (deletions.Count > 0)
            {
                var entityToRemove = _mapping.GetEntityByPrimaryKeyValue<T>(destinationCollection, deletions[0]);
                destinationCollection.Remove(entityToRemove);
                _dataContext.GetTable(typeof(T)).DeleteOnSubmit(entityToRemove);
                deletions.RemoveAt(0);
            }

            //Add all the entities that are in the source but not the destination
            foreach (var item in additions)
            {
                T newEntity = new T();
                T entityToAdd = _mapping.GetEntityByPrimaryKeyValue<T>(sourceCollection, item);
                Update(entityToAdd, newEntity, false);
                destinationCollection.Add(newEntity);
            }
            //Now update all the destination entities
            //TODO: could optimise this
            for (int i = 0; i < sourceCollection.Count; i++)
			{
                // Have removed the hard-coding of 'false' on the method call here in order
                // to allow recursion to happen. IncludeChildren is passed from the Update method 
                // in which we test to see if we've reached the max levels.
                Update(sourceCollection[i], destinationCollection[i], includeChildren); 
	        }
        }
    }
}
